# Vuex 的概念与基本使用

# Vuex 是什么?

Vuex 是一个专为 Vue.js 应用程序开发的全局状态管理库。它采用集中式存储管理应用的所有组件的状态,所有组件无论层级都可以获取到 Vuex 中存储的数据。

Vuex有五个核心概念:state、mutations、actions、getters 和 modules

# state

state 用于存储状态,也就是存储数据的地方。这是 Vuex 中唯一的数据源,并且每个应用中只能有唯一的一个 state 实例,这也就是为什么 Vuex 被成为集中式(中心化)的全局状态管理库。

值得注意的是,Vuex 中存储的数据是响应式的,也就是说,当 state 中的数据发生变化时,会自动驱动视图进行更新。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0 // 响应式数据
  },
	...
})

# mutations

mutations 是修改 store 中数据的唯一方式(尽管我们也可以通过 $store.state.xxx 的方式直接修改,但这种操作是不合法的,强烈建议不要使用这种方式,这样会使 devTools 追踪不到数据的变化)。

mutations 中的方法接收两个参数 statepayload,其中 state 就是 Vuex 中的 state,而 payload 则是我们传入的额外参数,由用户自主选择。

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
  },
  mutations: {
    increment(state, payload) {
      state.count += payload
    }
  },
  actions: {
  },
  modules: {
  }
})

mutations 在形式上更像事件而不是函数,因为我们无法通过函数的形式去调用它,而是要通过:

store.commit('increment',10)

这种 commit 的方式去触发它,其中第二个参数即是我们传入的额外参数。

需要注意的是:**mutations 中是不可以书写异步方法的!**因为异步方法会让 devtools 失效。具体来说,每次执行 mutation 中的方法时,devtools 都需要记录下执行前和执行后的 Vuex 状态。但如果 mutation 中含有异步任务,因为 devtools 无法得知该异步任务的完成时间,所以不知道什么时候去记录执行后的状态(或者说是 快照)。

如果需要根据异步方法的结果改变 state,那么就需要用到 actions 了

# actions

actions 和 mutations 非常相似,但是 actions 内可以书写异步代码。

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
  },
  mutations: {
    increment(state, payload) {
      state.count += payload
    }
  },
  actions: {
    increment(state, payload) {
      setTimeout(() => {
        state.commit('increment', payload)
      }, 1000);
    }
  },
  modules: {
  }
})

另外需要注意的是,我们只能通过 mutations 去修改 state 中的数据,所以,即使在 actions 中,也不可以直接修改状态,而是要通过调用 mutations 中的方法来间接操作 state 中的数据。

actions 中的方法接收两个参数,第一个参数是与 store 实例具有相同方法和属性的 context 对象,可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。第二个参数为额外参数。

在项目中,我们经常用到解构的方式来简化代码:

actions: {
    increment({commit}, payload) {
      setTimeout(() => {
        commit('increment', payload)
      }, 1000);
    }
  },

和 mutations 类似,我们调用 actions 中的方法需要通过:

$store.dispatch('increment',10)

这种 dispatch 方式来触发,其中第二个参数即是我们传入的额外参数。

# getters

getters 其实完全可以理解为就是 Vuex 中的计算属性,官方文档中也提到了,我们可以把 getters 看做是 store 的计算属性。(剧透:其实 getters 在底层就是借助 Vue 的计算属性实现的)

  getters: {
    number(state) {
      return state.count + 5
    }
  },
      
  $store.getters.number  // 访问 number

getters 可以接受两个参数,第一个参数是 state,第二个参数是其他的 getters,不常用。

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

# modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

通过 modules 可以解决上述问题,我们可以将较大的 state 分成一个个较小的 modules(模块),每个模块都拥有自己独立的 state、mutation、action、getter 和 modules,可以多次划分 modules,多层次嵌套 modules。

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

需要注意的是,尽管划分出了多个模块,但这些模块最终还是要归纳到统一的顶级父模块中,并不是完全独立的模块,这点和 pinia 中不同。pinia 是去中心化的架构,没有一个顶级的模块(或者说是仓库,因为 pinia 中已经没有 modules 的概念了),pinia 中允许创建多个完全独立的小模块,而 vuex 中只能从大的模块中使用 modules 划分出小模块,小模块依然和大的模块具有从属关系并不完全独立(并且从底层来说,所有小模块的 state 其实都是存在根模块上的)